package minio

import (
	"context"
	"fmt"
	"io"
	"time"

	"gitee.com/Luna-CY/Golang-Project-Template/internal/configuration"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/errors"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/logger"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/util/cryptor"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

func New(conf configuration.StorageProvider) (*Minio, errors.Error) {
	client, err := minio.New(conf.Minio.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(conf.Minio.AccessKey, conf.Minio.SecretKey, ""),
		Secure: conf.Minio.UseSSL,
	})

	if err != nil {
		logger.SugarLogger(logger.WithStack()).Errorf("ISS_GE.S_GE.GP_ER.230730: %v", err)

		return nil, errors.ErrorServerInternalError("ISS_GE.S_GE.GP_ER.323026", "创建Minio配置失败")
	}

	return &Minio{
		config: conf,
		client: client,
	}, nil
}

type Minio struct {
	config configuration.StorageProvider

	client *minio.Client
}

func (cls *Minio) GetName() string {
	return cls.config.Name
}

func (cls *Minio) GetBucket() string {
	return cls.config.Minio.Bucket
}

func (cls *Minio) PutObject(ctx context.Context, filepath string, size int64, reader io.Reader) errors.Error {
	_, err := cls.client.PutObject(ctx, cls.config.Minio.Bucket, filepath, reader, size, minio.PutObjectOptions{})
	if err != nil {
		return errors.ErrorServerInternalError("ISSM_IO.M_IO.PO_CT.462236", "上传对象失败")
	}

	return nil
}

func (cls *Minio) GetObject(ctx context.Context, filepath string) (io.ReadCloser, errors.Error) {
	object, err := cls.client.GetObject(ctx, cls.config.Minio.Bucket, filepath, minio.GetObjectOptions{})
	if err != nil {
		var err *minio.ErrorResponse
		if errors.As(err, &err) && err.Code == "NoSuchKey" {
			return nil, errors.New(errors.ErrorTypeRecordNotFound, "ISSM_IO.M_IO.GO_CT.634523", "对象不存在")
		}

		return nil, errors.ErrorServerInternalError("ISSM_IO.M_IO.GO_CT.552240", "获取对象失败")
	}

	return object, nil
}

func (cls *Minio) DeleteObject(ctx context.Context, filepath string) errors.Error {
	err := cls.client.RemoveObject(ctx, cls.config.Minio.Bucket, filepath, minio.RemoveObjectOptions{})
	if err != nil {
		var me *minio.ErrorResponse
		if errors.As(err, &me) && me.Code == "NoSuchKey" {
			return errors.New(errors.ErrorTypeRecordNotFound, "ISSM_IO.M_IO.DO_CT.762153", "对象不存在")
		}

		return errors.ErrorServerInternalError("ISSM_IO.M_IO.DO_CT.642241", "删除对象失败")
	}

	return nil
}

func (cls *Minio) GetObjectAccessedUrl(ctx context.Context, filepath string, timeout time.Duration) (string, errors.Error) {
	if cls.config.Cdn.Enable {
		return fmt.Sprintf("%s/%s", cls.config.Cdn.Domain, filepath), nil
	}

	if cls.config.IsPublic {
		return fmt.Sprintf("%s/%s", cls.config.S3V2.Endpoint, filepath), nil
	}

	url, err := cls.client.PresignedGetObject(ctx, cls.config.Minio.Bucket, filepath, timeout, nil)
	if err != nil {
		var me *minio.ErrorResponse
		if errors.As(err, &me) && me.Code == "NoSuchKey" {
			return "", errors.New(errors.ErrorTypeRecordNotFound, "ISSM_IO.M_IO.GOAU_RL.870754", "预签名对象失败")
		}

		return "", errors.ErrorServerInternalError("ISSM_IO.M_IO.GOAU_RL.742242", "预签名对象失败")
	}

	return url.String(), nil
}

func (cls *Minio) GetObjectHash(ctx context.Context, filepath string) (string, string, string, errors.Error) {
	object, rerr := cls.client.GetObject(ctx, cls.config.Minio.Bucket, filepath, minio.GetObjectOptions{})
	if rerr != nil {
		var err *minio.ErrorResponse
		if errors.As(rerr, &err) && err.Code == "NoSuchKey" {
			return "", "", "", errors.New(errors.ErrorTypeRecordNotFound, "ISSM_IO.M_IO.GOH_SH.904500", "对象不存在")
		}

		logger.SugarLogger(logger.WithStack()).Errorf("ISSM_IO.M_IO.GOH_SH.870637 获取对象失败 %v", rerr)

		return "", "", "", errors.ErrorServerInternalError("ISSM_IO.M_IO.GOH_SH.890700", "获取对象哈希值失败")
	}

	defer object.Close()

	// minio是懒加载，需要先触发一次读取才能知道文件是否存在
	var bs = make([]byte, 1024)
	if _, rerr = object.Read(bs); rerr != nil {
		if me, ok := rerr.(minio.ErrorResponse); ok {
			if me.Code == "NoSuchKey" {
				return "", "", "", errors.New(errors.ErrorTypeRecordNotFound, "ISSM_IO.M_IO.GOH_SH.1342850", "对象不存在")
			}
		}

		logger.SugarLogger(logger.WithStack()).Errorf("ISSM_IO.M_IO.GOH_SH.1372900 读取文件失败 %v", rerr)

		return "", "", "", errors.ErrorServerInternalError("ISSM_IO.M_IO.GOH_SH.1272840", "获取对象哈希值失败")
	}

	if _, err := object.Seek(0, io.SeekStart); err != nil {
		logger.SugarLogger(logger.WithStack()).Errorf("ISSM_IO.M_IO.GOH_SH.1383103 设置对象指针失败 %v", err)

		return "", "", "", errors.ErrorServerInternalError("ISSM_IO.M_IO.GOH_SH.1402940", "设置对象指针失败")
	}

	md5, sha1, sha256, err := cryptor.SumFile(object)
	if err != nil {
		return "", "", "", err.Relation(errors.ErrorServerInternalError("ISSM_IO.M_IO.GOH_SH.940706", "获取对象哈希值失败"))
	}

	return md5, sha1, sha256, nil
}
